﻿//-------------------------------------------------------------------------------------------------
// <copyright file="HttpRunner.cs" company="BeeWix">
// BeeWix. Licensed under MIT
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace BeeWix.Toolkit.Http
{
    using System;
    using System.IO;
    using System.Net;
    using System.Text;
    using System.Threading;
    
    /// <summary>
    /// Delegate prototype used to retreive response from the caching system
    /// </summary>
    /// <param name="request">The request url</param>
    /// <returns>The response data in a string form</returns>
    public delegate string GetFromCacheDelegate(string request);

    /// <summary>
    /// Delegate prototype used to add response in the caching system
    /// </summary>
    /// <param name="request">The request url</param>
    /// <param name="response">The response data in a string form</param>
    public delegate void AddToCacheDelegate(string request, string response);

    /// <summary>
    /// Helper used to deals with Http request from the code.
    /// </summary>
    public class HttpRunner
    {
        /// <summary>
        /// Delegate used to add response in the caching system
        /// </summary>
        private AddToCacheDelegate pAddToCacheDlg;

        /// <summary>
        /// Delegate Used to retreive response from the caching system
        /// </summary>
        private GetFromCacheDelegate pGetFrmCacheDlg;
        
        /// <summary>
        /// Used to store the response in binary form
        /// </summary>
        private byte[] pBtResponse;
        
        /// <summary>
        /// Used to store the content type
        /// </summary>
        private string pContentType;               
        
        /// <summary>
        /// Used to store request headers
        /// </summary>
        private WebHeaderCollection pHeads;
        
        /// <summary>
        /// Used to store the host
        /// </summary>
        private string pHost;
        
        /// <summary>
        /// Used to store The method type
        /// </summary>
        private RequestMethod pMethod;
        
        /// <summary>
        /// Used to store the response
        /// </summary>
        private string pResponse;
        
        /// <summary>
        /// Used to store the request url
        /// </summary>
        private string pUrl;
        
        /// <summary>
        /// Used to store the web request object
        /// </summary>
        private HttpWebRequest pWebReq;
        
        /// <summary>
        /// Used to store the web response object
        /// </summary>
        private HttpWebResponse pWebResp;

        /// <summary>
        /// Initializes a new instance of the <see cref="HttpRunner"/> class.
        /// </summary>
        /// <param name="url">The URL.</param>
        public HttpRunner(string url) : this(url, string.Empty, string.Empty, default(GetFromCacheDelegate), default(AddToCacheDelegate))
        {            
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="HttpRunner"/> class.
        /// </summary>
        /// <param name="url">The URL.</param>
        /// <param name="userName">Name of the user.</param>
        /// <param name="password">The password.</param>
        /// <param name="getFromCacheDlg">The get from cache DLG.</param>
        /// <param name="addToChacheDlg">The add to chache DLG.</param>
        public HttpRunner(string url, string userName, string password, GetFromCacheDelegate getFromCacheDlg, AddToCacheDelegate addToChacheDlg)
        {
            this.pUrl          = string.Empty;
            this.pMethod       = RequestMethod.Get;
            this.pWebReq       = null;
            this.pWebResp      = null;
            this.pHost         = string.Empty;
            this.pContentType  = string.Empty;
            this.pHeads        = null;
            this.pResponse     = string.Empty;
            this.pUrl          = url;
            this.pMethod       = RequestMethod.Get;
            this.pWebReq       = (HttpWebRequest)WebRequest.Create(this.pUrl);
            this.pWebReq.Proxy = null;

            if (!string.IsNullOrEmpty(userName))
            {
                this.pWebReq.Credentials = new NetworkCredential(userName, password);
            }

            this.pGetFrmCacheDlg = getFromCacheDlg;
            this.pAddToCacheDlg  = addToChacheDlg;
        }

        /// <summary>
        /// Enum representing the Request method type
        /// </summary>
        public enum RequestMethod
        {
            /// <summary>
            /// DELETE method
            /// </summary>
            Delete,

            /// <summary>
            /// GET method
            /// </summary>
            Get,

            /// <summary>
            /// POST method
            /// </summary>
            Post,

            /// <summary>
            /// PUT method
            /// </summary>
            Put
        }

        /// <summary>
        /// Enum representing Post encoding values
        /// </summary>
        public enum RequestPostEncoding
        {
            /// <summary>
            /// Ascii encoding
            /// </summary>
            ASCII,

            /// <summary>
            /// Unicode encoding
            /// </summary>
            Unicode,

            /// <summary>
            /// UTF8 encoding
            /// </summary>
            UTF8
        }

        /// <summary>
        /// Gets or sets the value of the Accept HTTP header.
        /// </summary>
        public string Accept
        {
            get
            {
                return this.pWebReq.Accept;
            }

            set
            {
                this.pWebReq.Accept = value;
            }
        }

        public HttpStatusCode LastErrorCode { get; set; }

        /// <summary>
        /// Gets the status of the response.
        /// </summary>
        public HttpStatusCode Status
        {
            get
            {
                if (this.pWebResp != null)
                {
                    return this.pWebResp.StatusCode;
                }
                else if (this.LastErrorCode != null)                
                {
                    return this.LastErrorCode;
                }
                else
                {
                    return HttpStatusCode.SeeOther;
                }
            }
        }        

        /// <summary>
        /// Executes the request.
        /// </summary>
        public void ExecuteRequest()
        {
            this.RunThreadRequest();
        }

        /// <summary>
        /// Executes the request to retreive binary data.
        /// </summary>
        public void ExecuteRequestBin()
        {
            Thread t = new Thread(new ThreadStart(this.RunThreadRequestBin));
            t.Start();

            while (t.ThreadState != ThreadState.Stopped)
            {
                Thread.Sleep(5);
            }
        }

         /// <summary>
        /// Gets the header.
        /// </summary>
        /// <param name="headerName">Name of the header.</param>
        /// <returns>Returns the header value</returns>
        public string GetHeader(string headerName)
        {
            string item;
            try
            {
                foreach (string s in this.pHeads)
                {
                    if (s == headerName)
                    {
                        item = this.pHeads[s];
                    }
                }
            }
            catch
            {
            }

            item = string.Empty;
            return item;
        }

         /// <summary>
        /// Gets the response.
        /// </summary>
        /// <returns>Returns the string data</returns>
        public string GetResponse()
        {
            return this.pResponse;
        }

        /// <summary>
        /// Gets the response bin.
        /// </summary>
        /// <returns>Returns the binary data</returns>
        public byte[] GetResponseBin()
        {
            return this.pBtResponse;
        }
        
        /// <summary>
        /// Sets the type of the content.
        /// </summary>
        /// <param name="ctn">The CTN.</param>
        public void SetContentType(string ctn)
        {
            pContentType = ctn;
            this.pWebReq.ContentType = ctn;
        }

        /// <summary>
        /// Sets the cookie.
        /// </summary>
        /// <param name="cookieName">Name of the cookie.</param>
        /// <param name="cookieValue">The cookie value.</param>
        public void SetCookie(string cookieName, string cookieValue)
        {
            if (this.pWebReq.CookieContainer == null)
            {
                this.pWebReq.CookieContainer = new CookieContainer();
            }

            this.pWebReq.CookieContainer.Add(new Cookie(cookieName, cookieValue, "/", this.pWebReq.RequestUri.Host));
        }

        /// <summary>
        /// Sets the method.
        /// </summary>
        /// <param name="met">The met.</param>
        public void SetMethod(RequestMethod met)
        {
            this.pMethod = met;
            
            switch (met)
            {
                case RequestMethod.Post:
                    this.pWebReq.Method = "POST";
                    break;
                case RequestMethod.Put:

                    this.pWebReq.Method = "PUT";
                    break;
                case RequestMethod.Delete:

                    this.pWebReq.Method = "DELETE";
                    break;
                default:
                    this.pWebReq.Method = "GET";
                    break;
            }
        }

         /// <summary>
        /// Sets the content of the post.
        /// </summary>
        /// <param name="cnt">The CNT.</param>
        /// <param name="enc">The enc.</param>
        public void SetPostContent(string cnt, RequestPostEncoding enc)
        {
            this.pWebReq.Method = this.pWebReq.Method == "GET" ? "POST" : this.pWebReq.Method;
            byte[] content = null;
            Stream strm = this.pWebReq.GetRequestStream();

            switch (enc)
            {
                case RequestPostEncoding.Unicode:
                    content = Encoding.Unicode.GetBytes(cnt);
                    break;
                case RequestPostEncoding.UTF8:
                    content = Encoding.UTF8.GetBytes(cnt);
                    break;
                case RequestPostEncoding.ASCII:
                    content = Encoding.ASCII.GetBytes(cnt);
                    break;
            }

            if (content != null)
            {
                strm.Write(content, 0, (int)content.Length);
            }

            strm.Close();
        }

        /// <summary>
        /// Runs the thread request bin.
        /// </summary>
        private void RunThreadRequestBin()
        {
            try
            {
                this.pWebResp   = (HttpWebResponse)this.pWebReq.GetResponse();
                Stream sr       = this.pWebResp.GetResponseStream();
                MemoryStream ms = new MemoryStream();
                int res         = 0;

                do
                {
                    res = sr.ReadByte();

                    if (res != -1)
                    {
                        ms.WriteByte((byte)res);
                    }
                } 
                while (res != -1);

                ms.Flush();
                this.pBtResponse = ms.ToArray();
                ms.Close();
                this.pHeads = this.pWebResp.Headers;
                this.pWebResp.Close();
                sr.Close();
            }
            catch
            {
            }
        }

        /// <summary>
        /// Runs the thread request.
        /// </summary>
        private void RunThreadRequest()
        {
            StreamReader sr;
            bool empty = true;

            try
            {
                empty          = this.pGetFrmCacheDlg == null;
                this.pResponse = empty ? string.Empty : this.pGetFrmCacheDlg(this.pUrl);
                empty          = this.pResponse == string.Empty;
            }
            catch (WebException ex)
            {
                sr             = new StreamReader(ex.Response.GetResponseStream());
                this.pResponse = sr.ReadToEnd();
                this.pWebResp = (HttpWebResponse)ex.Response;
                           sr.Close();
            }

            if (empty)
            {

                try
                {
                    this.pWebResp = (HttpWebResponse)this.pWebReq.GetResponse();
                    sr = new StreamReader(this.pWebResp.GetResponseStream(), true);
                    this.pResponse = sr.ReadToEnd();
                    this.pHeads = this.pWebResp.Headers;
                    this.pWebResp.Close();
                    sr.Close();

                    empty = this.pAddToCacheDlg == null;

                    if (!empty)
                    {
                        this.pAddToCacheDlg(this.pWebReq.RequestUri.OriginalString, this.pResponse);
                    }
                }
                catch (WebException ex)
                {
                    this.LastErrorCode = ((HttpWebResponse)ex.Response).StatusCode;
                }
            }
        }               
    }
}